package com.vision.sales.customer.cl.ui;

import static com.vision.sales.cl.SalesServices.customerSvc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.data.client.loader.RpcProxy;
import com.sencha.gxt.data.shared.IconProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.TreeStore;
import com.sencha.gxt.data.shared.loader.LoadResultListStoreBinding;
import com.sencha.gxt.data.shared.loader.PagingLoadConfig;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoader;
import com.sencha.gxt.theme.blue.client.grid.BlueGridAppearance;
import com.sencha.gxt.theme.blue.client.tree.BlueTreeAppearance;
import com.sencha.gxt.widget.core.client.TabItemConfig;
import com.sencha.gxt.widget.core.client.Window;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
import com.sencha.gxt.widget.core.client.event.HideEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.DateField;
import com.sencha.gxt.widget.core.client.form.NumberField;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor.DoublePropertyEditor;
import com.sencha.gxt.widget.core.client.form.SimpleComboBox;
import com.sencha.gxt.widget.core.client.form.TextArea;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.form.validator.MaxLengthValidator;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.toolbar.PagingToolBar;
import com.sencha.gxt.widget.core.client.treegrid.TreeGrid;
import com.sencha.gxt.widget.core.client.treegrid.TreeGridView;
import com.vision.core.cl.BaseModule;
import com.vision.core.cl.Callback;
import com.vision.core.cl.CoreValidators;
import com.vision.core.cl.event.CreatedEvent;
import com.vision.core.cl.event.CreatedEvent.CreationHandler;
import com.vision.core.cl.ui.ConfirmDialogHandler;
import com.vision.core.cl.ui.TabItemPanel;
import com.vision.core.cl.ui.UI;
import com.vision.core.cl.ui.component.BasicGridView;
import com.vision.core.cl.ui.component.EnumComboBox;
import com.vision.sales.cl.SalesServices;
import com.vision.sales.cl.SalesTasks;
import com.vision.sales.cl.data.TransactionEntryProperties;
import com.vision.sales.cl.event.CustomerCreatedEvent;
import com.vision.sales.cl.resource.SalesResources;
import com.vision.sales.cl.ui.component.CustomerComboBox;
import com.vision.sales.cl.ui.component.SalesmanComboBox;
import com.vision.sales.cl.ui.component.TransactionLoadConfig;
import com.vision.sales.cm.data.PaymentTerm;
import com.vision.sales.cm.db.data.AdvancePayment;
import com.vision.sales.cm.db.data.AppliedAdvancePayment;
import com.vision.sales.cm.db.data.Customer;
import com.vision.sales.cm.db.data.Customer.FieldSpecs;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.TransactionEntry;

/**
 * 
 * @author Mark
 *
 */
public class CustomersPanel extends TabItemPanel implements Editor<Customer> {

	interface Binder extends UiBinder<Widget, CustomersPanel> { }

	private static final Binder binder = GWT.create(Binder.class);
	
	@UiField(provided = true)
	NumberPropertyEditor<Double> doublePropertyEditor = new DoublePropertyEditor();
	
	@UiField(provided = true)
	NumberFormat numberFormat = NumberFormat.getFormat("0.00");
	
	@UiField VerticalLayoutContainer mainPanel;
	
	@Ignore @UiField TextButton refreshBtn;
	@Ignore @UiField TextButton addBtn;
	@Ignore @UiField TextButton deleteBtn;

	@Ignore @UiField CustomerComboBox customerComboBox;
	
	// Form fields that are bound by the form driver to the fields in the Customer class.
	// The name of these form fields must match the name of the field in the Customer class.
	@UiField TextField code;
	@UiField TextField name;
	@UiField TextArea address;
	@UiField TextArea deliverySite;
	@UiField TextField email;
	@UiField TextField phone;
	@UiField TextField fax;
	@UiField TextField contactName;
	@UiField TextField contactPhone;
	@UiField CheckBox withholdingTax;
	@UiField TextField tin;
	@UiField NumberField<Double> taxRate;
	@UiField CheckBox taxRateDirect;
	@UiField SimpleComboBox<PaymentTerm> paymentTerm;
	@UiField SalesmanComboBox salesman;
	@UiField CheckBox allowOverruns;
	@UiField NumberField<Double> overrunsRate;
	
	@UiField NumberField<Double> balance;
	
	/**
	 * The total amount of {@link AdvancePayment}s for the {@link Customer}
	 */
	@Ignore @UiField NumberField<Double> advance;
	
	/**
	 * The total amount of {@link AdvancePayment}s for the {@link Customer} that have
	 * been applied to {@link SalesInvoice}s.
	 */
	@Ignore @UiField NumberField<Double> advanceApplied;
	
	/**
	 * advance - advanceApplied
	 */
	@Ignore @UiField NumberField<Double> advanceBalance;
	
	@UiField
	TreeGrid<AdvancePaymentsTreeNode> appliedAdvancesTree;
	
	@Ignore @UiField TextButton saveBtn;
	@Ignore @UiField TextButton cancelBtn;

	// Interface used to bind the form fields to the User fields
	interface CustomerDriver extends SimpleBeanEditorDriver<Customer, CustomersPanel> { }
	
	// Instance of the CustomerDriver interface
	private CustomerDriver driver = GWT.create(CustomerDriver.class);
	
	@Ignore @UiField DateField startDate;
	@Ignore @UiField DateField endDate;
	@Ignore @UiField TextButton showBtn;
	
	@UiField(provided = true)
	Grid<TransactionEntry> ledgerGrid;
	
	@UiField
	PagingToolBar ledgerGridPagingToolbar;
	
	PagingLoader<PagingLoadConfig, PagingLoadResult<TransactionEntry>> ledgerGridLoader;
	
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	public CustomersPanel() {
		// Global Handlers -- should be registered only once
		BaseModule.addHandler(CustomerCreatedEvent.TYPE, new CreationHandler<Customer>() {
			@Override
			public void onCreated(CreatedEvent<Customer> e) {
				//grid.getStore().add(e.getObject());
				customerComboBox.getStore().add(e.getObject());
			}
		});
		
		appliedAdvancesTree = initializeAppliedAdvancePaymentsGrid();
		ledgerGrid = initializeLedgerGrid();
		
		setWidget(binder.createAndBindUi(this));
		
		// Initialize the driver, used to run the form editor
		driver.initialize(this);
		
		// Validators
		code.addValidator(new MaxLengthValidator(FieldSpecs.CODE_MAX));
		name.addValidator(new MaxLengthValidator(FieldSpecs.NAME_MAX));
		address.addValidator(new MaxLengthValidator(FieldSpecs.ADDRESS_MAX));
		deliverySite.addValidator(new MaxLengthValidator(FieldSpecs.DELIVERY_SITE_MAX));
		email.addValidator(CoreValidators.EMAIL_VALIDATOR);
		email.addValidator(new MaxLengthValidator(FieldSpecs.EMAIL_MAX));
		phone.addValidator(new MaxLengthValidator(FieldSpecs.PHONE_MAX));
		fax.addValidator(new MaxLengthValidator(FieldSpecs.FAX_MAX));
		contactName.addValidator(new MaxLengthValidator(FieldSpecs.CONTACT_MAX));
		contactPhone.addValidator(new MaxLengthValidator(FieldSpecs.CONTACT_PHONE_MAX));
		tin.addValidator(new MaxLengthValidator(FieldSpecs.TIN_MAX));
		
		{
			// List of sales invoices with advance payments
		}
		
		{
			startDate.setPropertyEditor(UI.datePropertyEditor());
			endDate.setPropertyEditor(UI.datePropertyEditor());
			

			Date firstDayOfYear = new Date(); // January 1
			firstDayOfYear.setMonth(0);
			firstDayOfYear.setDate(1);
			
			startDate.setValue(firstDayOfYear);
			endDate.setValue(new Date());
			
			// Initialize Loader for sales grid
			RpcProxy<PagingLoadConfig, PagingLoadResult<TransactionEntry>> proxy = new RpcProxy<PagingLoadConfig, PagingLoadResult<TransactionEntry>>() {
			    @Override
			    public void load(PagingLoadConfig loadConfig, AsyncCallback<PagingLoadResult<TransactionEntry>> callback) {
			    	Customer customer = customerComboBox.getCurrentValue();
			    	if (customer == null) {
			    		ledgerGrid.unmask();
			    		return;
			    	}
			    	
			    	TransactionLoadConfig tConfig = new TransactionLoadConfig(loadConfig);
			    	tConfig.setCustomers(Arrays.asList(new Customer[]{ customer }));
			    	tConfig.setStart(startDate.getValue());
			    	tConfig.setEnd(endDate.getValue());
			    	SalesServices.customerSvc().getTransactions(tConfig, callback);
			    }
		    };
		    
		    ledgerGridLoader = new PagingLoader<PagingLoadConfig, PagingLoadResult<TransactionEntry>>(proxy);
		    ledgerGridLoader.addLoadHandler(new LoadResultListStoreBinding<PagingLoadConfig, TransactionEntry, PagingLoadResult<TransactionEntry>>(ledgerGrid.getStore()));
	        
	        ledgerGrid.setLoader(ledgerGridLoader);
	        ledgerGrid.setLoadMask(true);
			
			ledgerGridPagingToolbar.bind(ledgerGridLoader);
		}
	}
		
	// UiFactory Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Ignore
	@UiFactory
	SimpleComboBox<PaymentTerm> initializePaymentTermCombo() {
		return new EnumComboBox<PaymentTerm>(PaymentTerm.values());
	}
	
	@SuppressWarnings("static-access")
	@Ignore
	@UiFactory
	TreeGrid<AdvancePaymentsTreeNode> initializeAppliedAdvancePaymentsGrid() {
		AdvancePaymentsTreeNodeProperties props = AdvancePaymentsTreeNodeProperties.INSTANCE;
		TreeStore<AdvancePaymentsTreeNode> store = new TreeStore<AdvancePaymentsTreeNode>(props.key);
		
		List<ColumnConfig<AdvancePaymentsTreeNode, ?>> columns = new ArrayList<ColumnConfig<AdvancePaymentsTreeNode, ?>>();
		columns.add(UI.column(props.advancePaymentNo, 50, "A/P No."));
		columns.add(UI.column(props.advancePaymentDate, 45, "A/P Date", UI.dateCell()));
		columns.add(UI.column(props.advancePaymentAmount, 50, "A/P Amount", UI.amountCell(),HasHorizontalAlignment.ALIGN_RIGHT));
		columns.add(UI.column(props.salesInvoiceNo, 45, "Inv. No."));
		columns.add(UI.column(props.dateApplied, 45, "Date", UI.dateCell()));
		columns.add(UI.column(props.appliedAmount, 50, "Applied Amount", UI.amountCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		columns.add(UI.column(props.balance, 50, "Balance", UI.amountCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		
		ColumnModel<AdvancePaymentsTreeNode> cm = new ColumnModel<AdvancePaymentsTreeNode>(columns);
		
		TreeGrid<AdvancePaymentsTreeNode> tree = new TreeGrid<AdvancePaymentsTreeNode>(store, cm, columns.get(0),
				new BlueGridAppearance(),
				new BlueTreeAppearance() {
					public ImageResource closeNodeIcon() {
						return null;
					}
					public ImageResource openNodeIcon() {
						return null;
					}
				}
		);
		tree.setBorders(true);
		tree.setAllowTextSelection(false);
		tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
		tree.setIconProvider(new IconProvider<AdvancePaymentsTreeNode>() {
			@Override
			public ImageResource getIcon(AdvancePaymentsTreeNode model) {
				return null;
			}
		});
		
		TreeGridView<AdvancePaymentsTreeNode> view = new TreeGridView<AdvancePaymentsTreeNode>();
		view.setAutoExpandColumn(columns.get(0));
		view.setForceFit(true);
		view.setAutoFill(true);
		view.setEmptyText(UI.text(2, "There are no advance payments to display."));
		tree.setView(view);
		
		return tree;
	}
	
	Grid<TransactionEntry> initializeLedgerGrid() {
		TransactionEntryProperties props = TransactionEntryProperties.INSTANCE;
		ListStore<TransactionEntry> store = new ListStore<TransactionEntry>(TransactionEntryProperties.KEY);
		
		List<ColumnConfig<TransactionEntry, ?>> columns = new ArrayList<ColumnConfig<TransactionEntry, ?>>();

		columns.add(UI.column(props.date(), 40, "Date", UI.dateCell()));
		columns.add(UI.column(props.typeText(), 100, "Transaction"));
		columns.add(UI.column(props.transactionRefNo(), 55, "Ref. No"));
		columns.add(UI.column(props.debit(), 55, "Debit", UI.decimalCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		columns.add(UI.column(props.credit(), 55, "Credit", UI.decimalCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		columns.add(UI.column(props.balance(), 55, "Balance", UI.decimalCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		
		ColumnModel<TransactionEntry> cm = new ColumnModel<TransactionEntry>(columns);

		Grid<TransactionEntry> grid = new Grid<TransactionEntry>(store, cm);
		grid.setAllowTextSelection(false);
		grid.getSelectionModel().setSelectionMode(SelectionMode.MULTI);
		
		GridView<TransactionEntry> view = new BasicGridView<TransactionEntry>();
		view.setAutoExpandColumn(columns.get(1));
		view.setEmptyText("There are no transactions to display.");
		grid.setView(view);
		
		return grid;
	}
	
	// UiHandler Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

	@UiHandler("customerComboBox")
	void customerComboBoxOnSelection(SelectionEvent<Customer> e) {
		Customer selectedCustomer = e.getSelectedItem();
		edit(selectedCustomer);
	}
	
	@UiHandler("refreshBtn")
	void refreshClicked(SelectEvent e) {
		loadData();
	}
	
	@UiHandler("addBtn")
	void addClicked(SelectEvent e) {
		Window w = (Window) new CreateCustomerWindow().asWidget();
		w.show();
	}
	
	@UiHandler("deleteBtn")
	void deleteClicked(SelectEvent e) {
		final Customer selected = customerComboBox.getCurrentValue(); 
		if (selected == null)
			return;
		
		UI.confirm("Confirm Delete", "Are you sure you want to delete '" + selected.getName() + "'?",
			new ConfirmDialogHandler() {
				@Override
				public void onYes(HideEvent event) {
					UI.mask(mainPanel, "Deleting " + selected.getName() + "...");
					customerSvc().delete(selected, new Callback<Customer>(mainPanel) {
						@Override
						protected void handleSuccess(Customer result) {
							//grid.getStore().remove(result);
							customerComboBox.getStore().remove(result);
							
							// Clear the form editor if necessary
							Customer inEditor = driver.flush();
							if (inEditor != null && inEditor.equals(result))
								edit(null);
						}
					});
				}
			}
		);
	}
	
	@UiHandler("saveBtn")
	void saveClicked(SelectEvent e) {
		Customer customer = driver.flush();
		if (driver.hasErrors())
			return;
		
		UI.mask(mainPanel, "Updating customer...");
		customerSvc().update(customer, new Callback<Customer>(mainPanel) {
			@Override
			protected void handleSuccess(Customer result) {
				//grid.getStore().update(result);
				customerComboBox.getStore().update(result);
				edit(result);
			}
		});
	}
	
	@UiHandler("cancelBtn")
	void cancelClicked(SelectEvent e) {
		edit(null);
	}
	
	@UiHandler("showBtn")
	void showLedgerClicked(SelectEvent e) {
		ledgerGridLoader.load();
	}
	
	// ITabItemPanel Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

	@Override
	public String getPanelId() {
		return SalesTasks.CUSTOMERS.getId();
	}


	@Override
	public TabItemConfig getConfig() {
		if (config == null) {
			config = new TabItemConfig("Customers", true);
			config.setIcon(SalesResources.INSTANCE.customers());
		}
		return config;
	}


	@Override
	public void loadData() {
		Log.debug("Loading customers...");
		
		UI.mask(mainPanel, "Loading customers...");
		customerSvc().getAll(new Callback<List<Customer>>(mainPanel) {
			@Override
			protected void handleSuccess(List<Customer> result) {
				customerComboBox.getStore().clear();
				customerComboBox.getStore().addAll(result);
				
				edit(null); // Clear the form
			}
		});
		
		salesman.load();
	}

	private void edit(final Customer customer) {
		driver.edit(customer);
		
		refreshUIControls(customer != null);
		
		if (customer == null)
			return;
		
		appliedAdvancesTree.getTreeStore().clear();
		
		UI.mask(appliedAdvancesTree, "Loading advance payments...");
		SalesServices.advancePaymentSvc().getAdvancePaymentsIncludingAppliedPayments(customer.getId(),
			new Callback<List<AdvancePayment>>(appliedAdvancesTree) {
				protected void handleSuccess(List<AdvancePayment> result) {
					double totalAdvance = 0.0d;
					double totalAdvanceApplied = 0.0d;
					
					for (AdvancePayment payment : result) {
						totalAdvance += payment.getAmount();
						
						AdvancePaymentsTreeNode paymentNode = new AdvancePaymentsTreeNode(payment);
						appliedAdvancesTree.getTreeStore().add(paymentNode);
						
						List<AppliedAdvancePayment> appliedPayments = new ArrayList<AppliedAdvancePayment>(payment.getAppliedAdvancePayments());
						
						double balance = payment.getAmount();
						// Sort the applied advance payments by date
						Collections.sort(appliedPayments, new Comparator<AppliedAdvancePayment>() {
							@Override
							public int compare(AppliedAdvancePayment arg0, AppliedAdvancePayment arg1) {
								return arg0.getDateApplied().compareTo(arg1.getDateApplied());
							}
						});
						
						for (AppliedAdvancePayment appliedPayment : appliedPayments) {
							// Compute for the running balance
							balance -= appliedPayment.getAppliedAmount();
							appliedPayment.setBalance(balance);
							
							appliedAdvancesTree.getTreeStore().add(paymentNode, new AdvancePaymentsTreeNode(appliedPayment));
							
							totalAdvanceApplied += appliedPayment.getAppliedAmount();
						}
					}
					
					appliedAdvancesTree.expandAll();
					
					advance.setValue(totalAdvance);
					advanceApplied.setValue(totalAdvanceApplied);
					advanceBalance.setValue(totalAdvance - totalAdvanceApplied);
				}
			}
		);
		
		ledgerGridLoader.load();
	}

	private void refreshUIControls(boolean enabled) {
		deleteBtn.setEnabled(enabled);
		
		saveBtn.setEnabled(enabled);
		cancelBtn.setEnabled(enabled);
		
		name.setEnabled(enabled);
		address.setEnabled(enabled);
		deliverySite.setEnabled(enabled);
		email.setEnabled(enabled);
		phone.setEnabled(enabled);
		fax.setEnabled(enabled);
		contactName.setEnabled(enabled);
		contactPhone.setEnabled(enabled);
		withholdingTax.setEnabled(enabled);
		tin.setEnabled(enabled);
		taxRate.setEnabled(enabled);
		taxRateDirect.setEnabled(enabled);
		paymentTerm.setEnabled(enabled);
		salesman.setEnabled(enabled);
		allowOverruns.setEnabled(enabled);
	}
	
}
